#pragma once
#include "../program.hpp"

namespace opengl {

/* Constructors */
OPENGLAPI program::program() : id(0) {}
OPENGLAPI program::program(program &&other) : id(other.id) { other.id = 0; }
OPENGLAPI program::program(program::shader_list shaders) { create(shaders); }
OPENGLAPI void program::create(shader_list shaders) {
    id = api::glCreateProgram();
    attach_shaders(shaders);
    link();
}

/* Member functions */
OPENGLAPI void program::attach_shader(const shader &shader) const { api::glAttachShader(id, shader.id); }
OPENGLAPI void program::attach_shaders(program::shader_list shaders) const {
    for (const auto &sh : shaders) {
        api::glAttachShader(id, sh.id);
    }
}
OPENGLAPI bool program::link() const {
    api::glLinkProgram(id);
    i32 status = 0;
    api::glGetProgramiv(id, 0x8B82, &status);
    return status == 1;
}
OPENGLAPI std::string program::get_error() const {
    i32 log_size = 0;
    api::glGetProgramiv(id, 0x8B84, &log_size);
    if (log_size <= 0) return "";
    std::string error_log(log_size + 1, '\0');
    api::glGetProgramInfoLog(id, log_size, nullptr, &error_log[0]);
    return error_log;
}
OPENGLAPI void program::use() const { api::glUseProgram(id); }

/* Uniform variables */
OPENGLAPI i32 program::get_uniform_location(const char *name) const { return api::glGetUniformLocation(id, name); }
OPENGLAPI void program::set_mat2f(u32 location, const f32 *value, bool transposed) const {
    api::glProgramUniformMatrix2fv(id, location, 1, transposed, value);
}
OPENGLAPI void program::set_mat3f(u32 location, const f32 *value, bool transposed) const {
    api::glProgramUniformMatrix3fv(id, location, 1, transposed, value);
}
OPENGLAPI void program::set_mat4f(u32 location, const f32 *value, bool transposed) const {
    api::glProgramUniformMatrix4fv(id, location, 1, transposed, value);
}
OPENGLAPI void program::set_vec2f(u32 location, const f32 *value) const {
    api::glProgramUniform2fv(id, location, 1, value);
}
OPENGLAPI void program::set_vec3f(u32 location, const f32 *value) const {
    api::glProgramUniform3fv(id, location, 1, value);
}
OPENGLAPI void program::set_vec4f(u32 location, const f32 *value) const {
    api::glProgramUniform4fv(id, location, 1, value);
}
OPENGLAPI void program::set_int32(u32 location, i32 value) const { api::glProgramUniform1i(id, location, value); }
OPENGLAPI void program::set_uint32(u32 location, u32 value) const { api::glProgramUniform1ui(id, location, value); }
OPENGLAPI void program::set_int64(u32 location, i64 value) const { api::glProgramUniform1i64(id, location, value); }
OPENGLAPI void program::set_uint64(u32 location, u64 value) const { api::glProgramUniform1ui64(id, location, value); }
OPENGLAPI void program::set_float32(u32 location, f32 value) const { api::glProgramUniform1f(id, location, value); }
OPENGLAPI void program::set_float64(u32 location, f64 value) const { api::glProgramUniform1d(id, location, value); }

/* Operators */
OPENGLAPI program &program::operator=(program &&other) {
    if (this != &other) {
        if (id != 0) {
            api::glDeleteProgram(id);
        }
        id = other.id;
        other.id = 0;
    }
    return *this;
}

/* Destructor */
OPENGLAPI program::~program() {
    if (id != 0) {
        api::glDeleteProgram(id);
    }
}

} // namespace opengl